package com.allwees.bs.module.core.dynamic.core;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.allwees.bs.core.modelbase.vo.R;
import com.allwees.bs.module.core.dynamic.annotation.DynamicInject;
import com.allwees.bs.module.core.dynamic.annotation.MultiDynamicInject;
import com.allwees.bs.module.core.dynamic.data.DynamicInjectQuery;
import com.allwees.bs.module.core.dynamic.strategy.DynamicInjectStrategy;
import com.allwees.core.common.util.reflect.ReflectionUtils;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


@Slf4j
@Aspect
@Setter
public class DynamicInjectAspect {

    private DynamicInjectQuery dynamicInjectQuery;

    @Pointcut("@annotation(com.allwees.bs.module.core.dynamic.annotation.DynamicInject) " +
            "|| @annotation(com.allwees.bs.module.core.dynamic.annotation.MultiDynamicInject)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object result = pjp.proceed();

        Set<DynamicInject> injects = new HashSet<>(3);

        add(injects, ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(MultiDynamicInject.class));
        add(injects, ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(DynamicInject.class));

        if(CollectionUtils.isEmpty(injects)){
            return result;
        }

        for (DynamicInject inject : injects) {
            if(inject == null){
                continue;
            }
            if (result instanceof R) {
                R r = (R) result;
                result = r.put("data", injectUnknownType(r.get("data"), inject));
            } else {
                result = injectUnknownType(result, inject);
            }
        }
        return result;
    }

    private Object injectUnknownType(Object data, DynamicInject inject) {
        if (data instanceof Page<?>) {
            data = inject(((Page<?>) data), inject);
        } else if (data instanceof List) {
            data = inject((List<?>) data, inject);
        } else {
            data = inject(data, inject);
        }
        return data;
    }


    private <T> Page<T> inject(Page<T> page, DynamicInject inject) {
        List<T> list = inject(page.getRecords(), inject);
        return page.setRecords(list);
    }

    @SuppressWarnings("rawtypes")
    private List inject(List records, DynamicInject inject) {
        List result = new ArrayList<>();
        for (Object obj : records) {
            result.add(inject(obj, inject));
        }
        return result;
    }

    private Object inject(Object result, DynamicInject inject) {
        if (StringUtils.isBlank(inject.key())) {
            return result;
        }

        Object key = getKey(result, inject.key().split("\\."));
        if (!(key instanceof String)) {
            return result;
        }

        Object target = getTargetObject(result, inject.target().split("\\."));

        DynamicInjectStrategy injectStrategy = DynamicInjectStrategy.strategy.get(inject.type());
        injectStrategy.setQuery(dynamicInjectQuery);
        return injectStrategy.inject((String) key, target);
    }

    public Object getTargetObject(Object obj, String[] expressions) {
        if (obj == null) {
            return null;
        }
        if (ArrayUtils.isEmpty(expressions) || (expressions.length == 1 && StringUtils.isBlank(expressions[0]))) {
            return obj;
        }
        return getTargetObject(ReflectionUtils.getProperty(obj, expressions[0]), ArrayUtils.remove(expressions, 0));
    }

    public Object getKey(Object obj, String[] keyExpress) {
        if (obj == null) {
            return null;
        }
        if (ArrayUtils.isEmpty(keyExpress)) {
            return obj;
        }
        return getKey(ReflectionUtils.getProperty(obj, keyExpress[0]), ArrayUtils.remove(keyExpress, 0));
    }

    private Set<DynamicInject> add(Set<DynamicInject> set, MultiDynamicInject multi) {
        if (multi == null || ArrayUtils.isEmpty(multi.value())) {
            return set;
        }
        add(set, multi.value());
        return set;
    }

    private Set<DynamicInject> add(Set<DynamicInject> set, DynamicInject... array) {
        if (ArrayUtils.isEmpty(array)) {
            return set;
        }
        set.addAll(Arrays.asList(array));
        return set;
    }
}
